/*
    This file is part of QTau
    Copyright (C) 2013-2018  Tobias "Tomoko" Platen <tplaten@posteo.de>
    Copyright (C) 2013       digited       <https://github.com/digited>
    Copyright (C) 2010-2013  HAL@ShurabaP  <https://github.com/haruneko>

    QTau is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

    SPDX-License-Identifier: GPL-3.0+
*/

#include "vosamp.h"
#include "../editor/ustjkeys.h"

#include <QFileInfo>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QTextStream>
#include <QDebug>
#include <QDirIterator>
#include <QStringList>
#include <QJsonDocument>

#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <QProcess>
#include <QtConcurrent/QtConcurrent>
#include <sndfile.h>
#include <unistd.h>
#include <sekai/midi.h>
#include <sekai/VoiceSampler.h>
#include <assert.h>
#include <sekai/PitchModel.h>

#define __devloglevel__ 4

//utilityfunctions -- move to libkawaii
bool fileExists(QString path) {
    QFileInfo check_file(path);
    // check if file exists and if yes: Is it really a file and no directory?
    return check_file.exists() && check_file.isFile();
}


class MySynth : public VoiceSampler
{
      float _score_end=0;
      QList<synthNote> _notes;
      PitchModel _pitch;
    protected:
      virtual float getF0(){return 0;}
      virtual bool addOnePulse();
    public:
      MySynth(int buffer_size): VoiceSampler(buffer_size) {}
      virtual void addNote(synthNote note){
          _notes.push_back(note);
          _pitch.addNote(note.start,note.lenght,frequencyFromNote(note.pitch));
          _score_end=note.start+note.lenght;
      }
      void fix()
      {
          _pitch.fix();
      }
};

static float* _impulse_response = nullptr;
static int _impulse_response_length = 0;
static int _samplerate = 0;

void load_impulse_response()
{
    SF_INFO info={0};
    SNDFILE* sf = sf_open("/home/isengaara/Musik/UTAU/voice/Iona_Beta/impulse_response.wav",SFM_READ,&info);
    assert(sf);
    _impulse_response = new float[info.frames];
    _impulse_response_length = info.frames;
    assert(info.channels==1);
    _samplerate = info.samplerate;
    sf_read_float(sf,_impulse_response,info.frames);
    sf_close(sf);
}


bool MySynth::addOnePulse()
{


        float endPos = _score_end;
        float currentIndex = inputPositionSamples();
        float currentTime = currentIndex / _samplerate;
        if(currentTime>endPos)
            return false;


        float f0=0;
        bool note_is_valid = false;


        //FIXME: this is not the best way
        for( int i=0; i<_notes.count(); ++i )
        {
           synthNote n = _notes[i];
           if(currentTime>n.start && currentTime<=n.start+n.lenght)
           {
               f0 = frequencyFromNote(n.pitch);
               note_is_valid = true;
           }
        }

        //new pitch model
        if(note_is_valid)
        {
            //float old_f0 = f0;
            f0 = _pitch.getF0atTime(currentTime);
            //DEVLOG_DEBUG("pitch_delta: "+STR(f0-old_f0));
        }



        if(f0)
        {
             float period = _samplerate / f0;
             ola(_impulse_response,_impulse_response_length,period);
        }
        else
        {
            float period = _samplerate / 500.0;
            float dummy = 0;
            ola(&dummy,1,period);
        }


        return true;



}




/// manifest
QString VoSamp::name()        { return "VoSamp"; }
QString VoSamp::description() { return "A real time singing synthesizer"; }
QString VoSamp::version()     { return "19.10"; }


/// setup
void VoSamp::setup(IController* ctrl)      {
    this->_ctrl = ctrl;
    this->_jack_samplerate = ctrl->sampleRate();

    load_impulse_response();

    if(_jack_samplerate!=_samplerate)
    {
        int err;
        _resampler = speex_resampler_init(1,_samplerate,_jack_samplerate,10,&err);
    }


    //thread queue signaling
    connect(this,&VoSamp::logDebug,this,&VoSamp::on_logDebug);
    connect(this,&VoSamp::logError,this,&VoSamp::on_logError);
    connect(this,&VoSamp::logSuccess,this,&VoSamp::on_logSuccess);

    _voices["HUMM"]="TODO";


}

bool VoSamp::setCacheDir(QString cacheDir)
{
    ///ONLY some backends use this
    if(cacheDir.length()) DEVLOG_DEBUG("cacheDir: "+cacheDir);
    return false;
}

bool VoSamp::synthIsRealtime()
{
    return true;
}


void VoSamp::lyricize(QString lyric)
{
    DEVLOG_DEBUG("VoSamp::lyricize: "+lyric);
}

bool VoSamp::synthesize(IScore *score)
{

    int buffer_size= 1024*8;
    MySynth* vsamp = new MySynth(buffer_size);

    for(int i=0;i<score->getNoteCount();i++)
    {
            auto n = score->getNote(i);
            vsamp->addNote(n);
    }
    vsamp->fix();

    _samp_fill = 1024 * 3;//zero on flush
    _samp = vsamp;

    if(_jack_samplerate<_samplerate)
    {
        DEVLOG_ERROR("down the rabbit hole");
        return false;
    }

    _ctrl->startPlayback(0);
    return true;
}

//
int VoSamp::readData(float *data, int size)
{
#ifdef VOSAMP_DEBUG
    static SNDFILE* debugFile=nullptr;

    if(debugFile==nullptr)
    {
        SF_INFO info={0};
        info.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
        info.samplerate = _jack_samplerate;
        info.channels = 1;
        debugFile = sf_open("/tmp/VoSamp_debug.wav",SFM_WRITE,&info);
        DEVLOG_DEBUG("open_debug_file");
    }

    if(size==0)
    {
        sf_close(debugFile);
        debugFile=nullptr;
        DEVLOG_DEBUG("close_debug_file");
    }
#endif

    if(_samp) {

        if(_jack_samplerate == _samplerate)
        {
            bool has_data = _samp->readData(data,size,_samp_fill);
            if(has_data==false) return 0;
        }
        else
        {
            spx_uint32_t in_size = (_samplerate*1.0*size/_jack_samplerate)+1;
            //spx_uint32_t in_size_orig = in_size;
            spx_uint32_t out_size = size;
            float in_data[in_size];
            bool has_data = _samp->readData(in_data,in_size,_samp_fill);
            if(has_data==false) return 0;
            speex_resampler_process_float(_resampler,0,in_data,&in_size,data,&out_size);
            assert(out_size==size);
            #ifdef VOSAMP_DEBUG
            sf_write_float(debugFile,data,size);
            #endif
            return out_size;
        }

    }

    return size;
}

/// phoneme transformation

QString VoSamp::getTranscription(QString txt)
{
    return txt; //extenal NLP
}


bool VoSamp::doPhonemeTransformation(QStringList& list)
{
    //TODO lyrizer
    DEVLOG_DEBUG(STR(list.count()));
    return false;
}

/// voice list (fix this there is only one)

bool VoSamp::setVoice(QString voiceName)
{
    if(_voices.keys().contains(voiceName))
    {
        _internalVoice = _voices[voiceName];
        return true;
    }
    else
    {
        _internalVoice = "";
        return false;
    }
}


QStringList VoSamp::listVoices()
{
    return _voices.keys();
}

/// logging (helper) (refactor this)
void VoSamp::on_logDebug(QString debug)
{
    _ctrl->logDebug(debug);
}

void VoSamp::on_logError(QString error)
{
    _ctrl->logError(error);
}

void VoSamp::on_logSuccess(QString success)
{
    _ctrl->logSuccess(success);
}

//remove cacheDir support
//FIX no sound
